//#define debug

//data structure for door info
struct doorInterface {
    unsigned char pinNum;
    unsigned char newState;
    unsigned long updateTime;
};

#define ULOCK_STATE LOW
#define LOCK_STATE  HIGH

//Start Global Variables
char cmd;                               //variable to store incomming command from the serial port
unsigned long cardNum = 0;              //variables to store the transmitted Card Number
unsigned int doorNum = 0;               //var to store the door number from serial port

//external variables
volatile unsigned long timer0_overflow_count;  //millis() counter reset

// Door definitions define pin number for Data 0 line, pin number for Data 1 line, pin number for Door output
const int doorDefinitions[][3] = {
  {20,21,13}
};

const int NUM_DOORS = (sizeof(doorDefinitions) / sizeof(int)) / 3;

// Door definitions define pin number for Data 0 line, pin number for Data 1 line, pin number for Door output
const int keyTranslations[][2] = {
  {0xE1,1},{0xD2,2},{0xC3,3},{0xB4,4},{0xA5,5},
  {0x96,6},{0x87,7},{0x78,8},{0x69,9},{0xF0,0},
  {0x4B, 10},{0x5A, 11}
};

doorInterface doorData[NUM_DOORS];
//End Global Variables

const int RFID_ACTIVE_PIN = 2;  //RFID_active debug pin

void setup()
{
  int k;
 
  Serial.begin(115200);
//  D_DIR&=~(DATA0+DATA1);
  pinMode(RFID_ACTIVE_PIN, OUTPUT);
  Serial.print("NUM_DOORS: ");
  Serial.println(NUM_DOORS, DEC);
  
  for (int idx=0;idx<NUM_DOORS;idx++) {
    Serial.print("Processing Reader: ");
    Serial.println(idx, DEC);
    pinMode(doorDefinitions[idx][0], INPUT);
    pinMode(doorDefinitions[idx][1], INPUT);
    pinMode(doorDefinitions[idx][2], OUTPUT);
    //add door info
    doorData[idx].pinNum = doorDefinitions[idx][2];
    //default new states to LOCK_STATE
    doorData[idx].newState = LOCK_STATE;
    //set newTime 0 to show no udpate pending
    doorData[idx].updateTime = 10;
  }
  //reset timer counteer to make sure doors are updated
  timer0_overflow_count = 0;
}

#define MAX_TIMEOUT  2000
unsigned long readRFID( int D0pin, int D1pin )
{
   unsigned long result = 0;                       //variable to store the result
   int i;                                          //loop index
   int timeout = 0;
   boolean comTimeout = 0;                         //var to show comm timeout
   boolean D0state,D1state;                        //variables to store pin states
   unsigned int bitsRead = 0;                      // Keep track of the number of bits read
   
   //this function is called because on of the signal pins for the reader went low, check to make sure the signal is valid
   //both signals should not be in the same state
   //start by reading the pin states
   D0state = digitalRead(D0pin);
   D1state = digitalRead(D1pin);
   if( D0state == D1state )
   {
#ifdef debug
      //not valid signal state, give up
      Serial.println("RFID read Failed at start, D0pin == D1pin");
#endif
      return 0xFFFFFFFF;
   }
   
   // Go through reading bits until we hit a timeout
   while (!comTimeout) {
     //check that they are in a valid state, if not then give up
     if( D0state == 1 && D1state == 0 ) {
       //valid bit is logic 1
       result = result<<1;
       result++;                //shift bits up 1
       bitsRead++;
     }
     else if( D0state ==0 && D1state == 1 ) {
       //valid bit, logic 0
       result = result<<1;                    //shift bits up 1
       bitsRead++;
     }
     else {
#ifdef debug
       Serial.print("RFID val0 failed D1state == D0state at loop index ");
       Serial.println(i,DEC);
       if(digitalRead(D0pin) == HIGH )
         Serial.println("D0state = 1");
       if(digitalRead(D1pin) == HIGH )
         Serial.println("D1state = 1");
       if(D0state)
         Serial.println("D0state = 1");
       if(D1state)
         Serial.println("D1state = 1");
#endif
       return 0xFFFFFFFF;
     }
#ifdef debug
     Serial.print("Bits read: ");
     Serial.println(bitsRead, DEC);
#endif
     // Wait for signals to return to neutral
     while( ( D0state != D1state ) && !comTimeout ) {
       D0state = digitalRead(D0pin);
       D1state = digitalRead(D1pin);
       timeout++;
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;  
     }
     // Next wait for next bit transition
     timeout = 0;
     while( ( D0state == D1state ) && !comTimeout ) {
       D0state = digitalRead(D0pin);
       D1state = digitalRead(D1pin);  
       timeout++;
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;  
     }

   }
   
   if (bitsRead == 8) {
     for (int idx=0;idx<12;idx++) {
#ifdef  debug
       Serial.print("Translation value: ");
       Serial.println(keyTranslations[idx][0], DEC);
       Serial.print("Result: ");
       Serial.println(result, DEC);
#endif
       if (keyTranslations[idx][0] == result) {
         result = keyTranslations[idx][1];
         break;
       }
     }
#ifdef  debug
     Serial.print("8 bit value: ");
     Serial.println(result, DEC);
#endif 
   }
   else if (bitsRead == 26) {
     result = result>>1;
     result = result & 0xFFFFFF;
#ifdef  debug
     Serial.print("26 bit value: ");
     Serial.println(result, DEC);
#endif
   }
   else if (bitsRead == 34) {
     result = result>>1;
     result = result & 0xFFFFFFF;
#ifdef  debug
     Serial.print("34 bit value: ");
     Serial.println(result, DEC); 
#endif
   }
#ifdef  debug
   Serial.print("Returning result: ");
   Serial.println(result, DEC); 
#endif
   return result; 
}
/***********************************************************
* cmdProcess: this function processes the commands that have
* have already been parsed from the serial port
*
************************************************************/
#define ULOCK_TIME_MS 4000  //unlock for 4s  
#define UNLOCK 'U'          //unlock cmd value
#define LOCK   'L'          //lock cmd value
#define ADMIT_ACCESS  'A'   //temporarily unlock door
#define STATUS  'S'         //status command

void cmdProcess()
{
  switch( cmd ){
   
  case UNLOCK:
#ifdef  debug
    Serial.print("UNLOCK door ");
    Serial.println( doorNum,DEC );
#endif
    // Unlock the door corresponding to doorNum
    if( doorNum < NUM_DOORS )            //make sure we don't go outside our array
    {
      //direct access so we don't really need to update the updateTime etc
      digitalWrite( doorData[doorNum].pinNum, ULOCK_STATE );
      //record the new state
      doorData[doorNum].newState = ULOCK_STATE;
     
    }
    else  //fail
    {
      //give some bad feedback
    }
    break;
   
  case LOCK:
#ifdef  debug
    Serial.print("LOCK door ");
    Serial.println( doorNum,DEC );
#endif
    // Lock the door corresponding to doorNum
    if( doorNum < NUM_DOORS )            //make sure we don't go outside our array
    {
      //direct access so we don't really need to update the updateTime etc
      digitalWrite( doorData[doorNum].pinNum, LOCK_STATE );
      //record the new state
      doorData[doorNum].newState = LOCK_STATE;
    }
    else  //fail
    {
      //give some bad feedback
    }
    break;
 
    case ADMIT_ACCESS:
#ifdef  debug
    Serial.print("GRANT door ");
    Serial.println( doorNum,DEC );
#endif
    //temporarily unlock door
    if( doorNum < NUM_DOORS )            //make sure we don't go outside our array
    {
     
      //direct access so we don't really need to update the updateTime etc
      digitalWrite( doorData[doorNum].pinNum, ULOCK_STATE );
      //setup door to be locked in the future
      doorData[doorNum].newState = LOCK_STATE;
      doorData[doorNum].updateTime = millis() + ULOCK_TIME_MS;
    }
     
  case STATUS:
#ifdef  debug
    Serial.print("status ");
#endif
    //respond over serial with current status
    break;
  default:
    //invalid command
    break;
  }
}

/***********************************************************
* function to parse incomming serial stream for instructions
*
* format is command,data0(1 byte max),data1(10 byte max) \n
************************************************************/
#define BUF_LEN 10
char parseIndex = 0;                  //index to keep track of what step we are at
char inBuf[BUF_LEN];               //string buffer
char inByte = 0;                      //single byte tmp buffer
char lastByte = 0;                    //previous byte tmp buffer
//char cmd = 0;                         //var to store the cmd in
int data0 = 0;                        //var to store data0 for cmd
int data1 = 0;                        //var to store data1 for cmd (if exists
int j = 0;                            //index for keeping track of position in buffer
//long cardNum = 0;                     //variable to store the decoded card number
boolean parseSerial()
{
 
  while( Serial.available() )          //process all bytes in the buffer
  {
    inByte = Serial.read();            //read the next byte from the serial buffer
#ifdef debug2    
    Serial.print("\nparseIndex = ");
    Serial.println(parseIndex, DEC);
    Serial.print("inByte = ");
    Serial.println(inByte);
#endif
    //check for CR
    if( lastByte == '\n' || lastByte == ' ' )
    {
#ifdef debug2    
      Serial.print("\n");
      Serial.print("reset, inByte = ");
      Serial.println(inByte);
#endif
      parseIndex = 0;
    }
     
    if( parseIndex == 0 )
    {
#ifdef debug2
        Serial.println(" in ParseIndex = 0 function");
 #endif
        if( inByte != '\n' && inByte != ' ' && ( lastByte == '\n' || lastByte == ' ' ) )
        {
          //increment the parseIndex so that we can get along with it..
          parseIndex++;  
#ifdef debug2
          Serial.println("parseIndex++");
 #endif          
          //don't break out of switch() though, so that we loop through again with the same byte
        }
    }
    if( parseIndex == 1 )
    {
       
        cmd = inByte;
        //increment the parse index, and break so that we read in the next byte
#ifdef debug
        Serial.print("\nreceived cmd byte: ");
        Serial.println(cmd);
#endif
        parseIndex++;
    }
    else if( parseIndex == 2 )
    {
        if( inByte != ',' && lastByte == ',' )
        {
          //increment the parseIndex so that we can get along with it..
#ifdef debug2
          Serial.print("\nreset index j");
#endif
          parseIndex++;  
          //reset buffer index
          j = 0;          
          //don't break out of switch() though, so that we loop through again with the same byte
        }
    }
    if( parseIndex == 3 )
    {
        if( inByte >= '0' && inByte <= '9' )
        {
          inBuf[j] = inByte;         //add new byte
          j++;
          break;
        }
        else     //end of the data bytes
        {  
          //add a null character to end of inBuf
          inBuf[j] = NULL;
          //start by parsing the data array
          doorNum = atol( inBuf );
#ifdef debug
          Serial.print("\ndoor Num: ");
          Serial.print(doorNum,DEC);
          //Serial.write(inBuf,0);
          Serial.print("\n");
#endif

          //move on to the next step
          parseIndex++;
        }
    }
    if( parseIndex == 4 )
    {
        if( inByte != ',' && lastByte == ',' )
        {
          //increment the parseIndex so that we can get along with it..
#ifdef debug2
          Serial.print("\nreset index j");
#endif
          parseIndex++;  
          //reset buffer index
          j = 0;          
          //don't break out of switch() though, so that we loop through again with the same byte
        }
    }
    if( parseIndex == 5 )
    {
        if( inByte >= '0' && inByte <= '9' )
        {
          inBuf[j] = inByte;         //add new byte
          j++;
          break;
        }
        else     //end of the data bytes
        {  
          //add a null character to end of inBuf
          inBuf[j] = NULL;
          //start by parsing the data array
          cardNum = atol( inBuf );
#ifdef debug
          Serial.print("\nCard Num: ");
          Serial.print(cardNum,DEC);
          //Serial.write(inBuf,0);
          Serial.print("\n");
#endif

          //move on to the next step
          parseIndex++;
        }
    }
    if( parseIndex == 6 )
    {
        //if we got here, we have full success
        parseIndex = 0;  
        return true;    
    }
   
    //read next byte
    lastByte = inByte;    
   
  }
  //if we got to here, we do not have fresh data
  return false;
 
}

/*************************************************************
* updateLock checks to see if a lock state needs to be changed,
* or, resets the millis() counter so that we never overrun the
* counter
*************************************************************/

void updateLock()
{
  int i;
  //loop though the array
  for( i=0;i<NUM_DOORS;i++ ) {
    //check if lock needs to be changed, timer is valid and expired
    if( doorData[i].updateTime < millis() && doorData[i].updateTime > 0 ) {
      //udpate door state
      digitalWrite( doorData[i].pinNum, doorData[i].newState );
      //update the time so we don't repeat this
      doorData[i].updateTime = 0;
    }
  }
  //loop through array, if all udpateTimes are invalid, reset millis() so we don't overrun
  for( i=0;i<NUM_DOORS;i++ ) {
    //if any of the updateTimes are valid, then give up
    if( doorData[i].updateTime > 0 ) {
      return;
    }
  }
  //if we reached here then all updateTimes are invalid so we can reset millis()
  timer0_overflow_count = 0;
}
 
void loop()
{
  Serial.println("Listening for card");
  for(;;) {
    unsigned long cardValue = 0xFFFFFFFF;
    // Process each card reader defined
    for (int idx=0;idx<NUM_DOORS;idx++) {
      //Serial.print("Checking Reader: ");
      //Serial.println(idx, DEC);
      cardValue = 0xFFFFFFFF;
      if( digitalRead( doorDefinitions[idx][0] ) != digitalRead( doorDefinitions[idx][1] ) ) {
#ifdef debug
        Serial.print("Pin change on Reader: ");
        Serial.println(idx, DEC);
#endif
        cardValue = readRFID (doorDefinitions[idx][0], doorDefinitions[idx][1]);
#ifdef debug
        Serial.print("Card Value: ");
        Serial.println(cardValue, DEC);
#endif        
      }
      //if we have read a valid card, then we need to tell the mother ship
      if( cardValue != 0xFFFFFFFF ) {
        //send command showing that we have read a card
        Serial.print("R,");
        Serial.print(idx, DEC);
        Serial.print(",");
        Serial.println(cardValue,DEC);
      }
#ifdef debug
      //Otherwise if we did not receive a valid card number, send out error message
      else {
        Serial.println("E,2,Card read failed");
      }
#endif

    }  
    //process any waiting serial
    if( parseSerial() )
    {
      //execute the received command
      cmdProcess();
#ifdef debug
      //we have an updated command
      Serial.print("\nnew command ");
      Serial.write( cmd );
      Serial.print("\n");
      Serial.print("door number ");
      Serial.println( doorNum,DEC );
      Serial.print("card number ");
      Serial.println( cardNum,DEC );
#endif
    }
    // Check if we need to change the state of a lock
    updateLock();
  } 
}
